// Date:   Wed Aug 13 12:47:54 PM 2025
// Mail:   lunar_ubuntu@qq.com
// Author: https://github.com/xiaoqixian

#pragma once

#include "yaml-cpp/exceptions.h"
#include "yaml-cpp/node/node.h"
#include <cstddef>
#include <opencv2/opencv.hpp>
#include <opencv2/opencv.hpp>
#include <openvino/openvino.hpp>
#include <string>
#include <type_traits>
#include <utility>
#include <functional>
#include <random>

extern "C" {
#include <libavutil/frame.h>
}

#ifndef NDEBUG
#include <chrono>
#define TIME_COST(call, open) do {\
  if constexpr(open) {\
    auto const start = std::chrono::high_resolution_clock::now();\
    call;\
    auto const end = std::chrono::high_resolution_clock::now();\
    auto const dur = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);\
    printf("%s cost %ldms\n", #call, dur.count());\
  } else {\
    call;\
  }\
} while (0)
#else
#define TIME_COST(call, open) call
#endif

constexpr size_t WINDOW_SIZE = 5;
constexpr size_t MAX_SAMPLE_RATE = WINDOW_SIZE * 2;
constexpr int BATCH_SIZE = 1;

inline long now_ms() {
  return std::chrono::duration_cast<std::chrono::milliseconds>(
           std::chrono::system_clock::now().time_since_epoch()
       ).count();
}

inline std::string timestr() {
  auto now = std::chrono::system_clock::now();
  std::time_t t = std::chrono::system_clock::to_time_t(now);
  std::tm tm = *std::localtime(&t);

  std::ostringstream oss;
  oss << std::put_time(&tm, "%Y-%m-%d-%H-%M-%S");
  return oss.str();
}

inline int randint() {
  static std::mt19937 gen(std::random_device{}());  // static generator
  std::uniform_int_distribution<int> dist;
  return dist(gen);
}


struct YoloBox {
  float x, y, w, h;
  float score;
  int class_id;
};

class RunningAverage {
private:
  double avg_ = 0.0;
  std::size_t count_ = 0;

public:
  template <typename T>
  double operator+=(T x) {
    count_++;
    avg_ += (static_cast<double>(x) - avg_) / count_;
    return avg_;
  }

  operator double() const {
    return avg_;
  }
};

// 根据frame帧大小将一个bbox转换为cv::Rect，防止出界
inline cv::Rect bbox_to_rect(const YoloBox& bbox, const cv::Mat& frame) {
  int x = static_cast<int>(bbox.x);
  int y = static_cast<int>(bbox.y);
  int w = static_cast<int>(bbox.w);
  int h = static_cast<int>(bbox.h);

  // 裁剪到 frame 边界
  x = std::max(0, x);
  y = std::max(0, y);

  if (x + w > frame.cols) w = frame.cols - x;
  if (y + h > frame.rows) h = frame.rows - y;

  // 防止宽高变负
  w = std::max(0, w);
  h = std::max(0, h);

  return cv::Rect(x, y, w, h);
}


ov::Tensor mat_to_tensor(const cv::Mat& img, int target_w, int target_h);

float frame_diff_ratio(cv::Mat const&, cv::Mat const&);

int weighted_random_sample(std::vector<std::size_t> const& weights);

void mat_preprocess(const cv::Mat&, std::size_t, ov::Tensor&);
void av_preprocess(AVFrame*, std::size_t, ov::Tensor&);
cv::Mat avframe_to_cvmat(AVFrame*);

template <class F>
class ScopeGuard {
public:
  template <class G,
        std::enable_if_t<std::is_constructible_v<F, G&&>, int> = 0>
  explicit ScopeGuard(G&& g) noexcept(std::is_nothrow_constructible_v<F, G&&>)
    : func_(std::forward<G>(g)), active_(true) {}

  ScopeGuard(const ScopeGuard&) = delete;
  ScopeGuard& operator=(const ScopeGuard&) = delete;

  ScopeGuard(ScopeGuard&& other) noexcept(
    std::is_nothrow_move_constructible_v<F>)
    : func_(std::move(other.func_)), active_(other.active_) {
    other.active_ = false;
  }

  ~ScopeGuard() noexcept {
    if (active_) {
      try { std::invoke(func_); } catch (...) { /* 吞掉异常，避免析构时终止 */ }
    }
  }

  void dismiss() noexcept { active_ = false; }

private:
  F   func_;
  bool active_;
};

template <class Fn>
auto make_scope_guard(Fn&& fn) {
  using F = std::decay_t<Fn>;
  return ScopeGuard<F>(std::forward<Fn>(fn));
}

bool save_cvmat(std::string const&, cv::Mat const&);

template <typename T>
T yaml_get_or_default(YAML::Node node, std::string const& key, T value) {
  try {
    return node[key].as<T>();
  } catch (YAML::Exception const& e) {
    return value;
  }
}

inline void throw_av_error(const int err) {
  char errbuf[128];
  av_strerror(err, errbuf, sizeof(errbuf));
  throw std::runtime_error(errbuf);
}

struct TimeMeasureSpan {
  using Ms = std::chrono::milliseconds;
  using MeasureInfo = std::unordered_map<std::string, std::pair<Ms, size_t>>;
  static thread_local MeasureInfo time_measure_info;
  using Instant = decltype(std::chrono::high_resolution_clock::now());
  Instant p1, p2;
  std::string name;
  size_t cnt = 1;
  TimeMeasureSpan(std::string const& nm) noexcept: 
    p1(std::chrono::high_resolution_clock::now()),
    name(nm)
  {}
  TimeMeasureSpan(std::string const& nm, size_t n) noexcept: 
    p1(std::chrono::high_resolution_clock::now()),
    name(nm),
    cnt(n)
  {}

  ~TimeMeasureSpan() noexcept {
    p2 = std::chrono::high_resolution_clock::now();
    auto const d = p2 - p1;
    auto const it = time_measure_info.try_emplace(name, Ms{0}, size_t{0}).first;
    it->second.first += std::chrono::duration_cast<Ms>(d);
    it->second.second += cnt;
  }
};

